﻿using System;
using System.Collections.Generic;

namespace Monti.MVVM.Services
{
   /// <summary>
   /// Manager for required services.
   /// </summary>
   public static class ServiceManager
   {
      private static readonly Dictionary <Type, Service> s_Services;

      /// <summary>
      /// Static constructor.
      /// </summary>
      static ServiceManager ()
      {
         s_Services = new Dictionary<Type, Service> ();
      }

      /// <summary>
      /// Number of services available.
      /// </summary>
      public static int ServicesCount { get { return s_Services.Count; } }

      /// <summary>
      /// Registers a service.
      /// </summary>
      public static void Register<TInterface, TImplementation> () where TImplementation : TInterface
      {
         Register<TInterface, TImplementation> (false, null);
      }

      /// <summary>
      /// Registers a service type as a singleton.
      /// </summary>
      public static void RegisterSingleton<TInterface, TImplementation> () where TImplementation : TInterface
      {
         Register<TInterface, TImplementation> (true, null);
      }

      /// <summary>
      /// Registers a service as a singleton.
      /// </summary>
      /// <exception cref="ArgumentNullException">If service is of null.</exception>
      public static void RegisterSingleton<TInterface, TImplementation> (TImplementation service) where TImplementation : class, TInterface
      {
         Register<TInterface, TImplementation> (true, service);
      }

      /// <summary>
      /// Registers a service.
      /// </summary>
      /// <param name="isSingleton">true if service is Singleton; otherwise false.</param>
      /// <param name="service">A default implementation or null.</param>
      private static void Register<TInterface, TImplementation> (bool isSingleton, object service) where TImplementation : TInterface
      {
         s_Services.Add (typeof (TInterface), new Service (typeof (TImplementation), isSingleton, service));
      }

      ///<summary>
      /// Adds a precreated service to the servvice type queue.
      ///</summary>
      ///<param name="service">Precreated service.</param>
      ///<typeparam name="TInterface">Interface Type of precreated service</typeparam>
      ///<typeparam name="TImplementation">Implentation Type of precreated type.</typeparam>
      public static void Enqueue<TInterface, TImplementation> (TImplementation service) where TImplementation : TInterface
      {
         var serviceInformation = s_Services[typeof (TInterface)];
         if (serviceInformation.IsSingleton)
            throw new InvalidOperationException ("Add can not be called for singleton type service interface!");
         serviceInformation.ServiceImplementations.Enqueue (service);
      }

      /// <summary>
      /// Resolves a service.
      /// </summary>
      public static TInterface Resolve<TInterface> ()
      {
         if (s_Services.ContainsKey(typeof (TInterface)))
            return (TInterface) s_Services [typeof (TInterface)].Implementation;
         return default (TInterface);
      }

      /// <summary>
      /// Removes all services.
      /// Used by unit test mainly.
      /// </summary>
      public static void RemoveAllServices ()
      {
         s_Services.Clear ();
      }

      /// <summary>
      /// A service that is registered with the service manager.
      /// </summary>
      private class Service
      {
         private readonly Type m_ServiceType;
         private object m_ServiceImplementation;

         /// <summary>
         /// Indicates if the service is a singleton.
         /// </summary>
         public bool IsSingleton { get; private set;}

         /// <summary>
         /// Queue list of objects precreated to support the service.
         /// </summary>
         public Queue<object> ServiceImplementations { get; private set; }

         /// <summary>
         /// Initializes a new instance of the <see cref="Service"/> class.
         /// </summary>
         /// <param name="serviceType">Type of the service.</param>
         /// <param name="isSingleton">Whether the service is a Singleton.</param>
         /// <param name="serviceImplementation">A default implementation or null.</param>
         public Service (Type serviceType, bool isSingleton, object serviceImplementation)
         {
            if (!isSingleton)
               this.ServiceImplementations = new Queue <object> ();
            this.m_ServiceType = serviceType;
            this.IsSingleton = isSingleton;
            this.m_ServiceImplementation = serviceImplementation;
         }

         /// <summary>
         /// Gets the service implementation.
         /// </summary>
         public object Implementation
         {
            get
            {
               if (this.IsSingleton)
                  return this.m_ServiceImplementation ?? (this.m_ServiceImplementation = Activator.CreateInstance (this.m_ServiceType));
               if (this.ServiceImplementations.Count > 0)
                  return this.ServiceImplementations.Dequeue ();
               return Activator.CreateInstance (this.m_ServiceType);
            }
         }
      }
   }
}
